package com.mybatis.mapper;

import basic.framework.components.mybatis.common.utils.ModelUtils;
import com.mybatis.dto.MyEntityDto;
import com.mybatis.util.MyModelUtil;
import com.mybatis.util.MyReflectionUtils;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;

public class MyCrudInjector {
    public static final String METHOD_GET_LIST = "getEntityList";
    //这边可以不用引进curdprovider，可以直接将其方法都变为静态方法
    private MapperBuilderAssistant assistant;
    private Configuration configuration;
    private static final XMLLanguageDriver driver = new XMLLanguageDriver();//用来创建sqlsource

    public MyCrudInjector(Configuration configuration)
    {
        this.configuration = configuration;
    }


    public <T> void inject(Class<T> mapperClass) {//为接口里面的每个方法实现
        this.assistant =
                new MapperBuilderAssistant(configuration,mapperClass.getName().replaceAll("\\.","/"));
        //第二个参数本来应该是xml文件的resource
        //感觉上面第二个参数随便写也没什么区别啊,是的，就算youxml也早就加载过了，改添加的mappedstatement也加了
        this.assistant.setCurrentNamespace(mapperClass.getCanonicalName());
        //根据java缺省api获取泛型
        Class modelClass = MyReflectionUtils.extractModelClass(mapperClass);
        MyEntityDto myEntityDto = MyModelUtil.generateModelDto(modelClass);
        //项目组都是传的两个class，然后在获得myentitydto，
        // 感觉多此一举啊，我可以传一个，也可以传三个,虽然都是从map中获取的，提升了速度，不用每次计算
        //传三个吧，不然每次都要计算获得entitydto，感觉浪费计算性能,第二个感觉也没用
        this.injectInsertSql(mapperClass,modelClass,myEntityDto);
        this.injectDeleteSql(mapperClass,modelClass,myEntityDto);
        // 锁总判断是否youpridto，才加载这些通过id，删除，更新，查询等接口，
        // 也应该去判断，此处就不加了，因为我们的表肯定要有主键的，这是一个好习惯
        this.injectUpdateByIdSql(mapperClass, modelClass,myEntityDto);
        this.injectUpdateByWholeEntitySql(mapperClass, modelClass,myEntityDto);
        this.injectGetByIdSql(mapperClass, modelClass,myEntityDto);
        this.injectGetEntityCount(mapperClass, modelClass,myEntityDto);
        this.injectGetValueById(mapperClass, modelClass,myEntityDto);
        this.injectinsertBatch(mapperClass, modelClass,myEntityDto);
        this.injectDeleteBatch(mapperClass, modelClass,myEntityDto);
        this.injectgetEntityListForPage(mapperClass, modelClass,myEntityDto);

        this.injectGetListSql(mapperClass, modelClass, METHOD_GET_LIST,myEntityDto);
    }

    private <T> void injectGetListSql(Class<T> mapperClass, Class modelClass, String methodGetList, MyEntityDto myEntityDto) {
        this.addMappedStatement(mapperClass, methodGetList,
                createSqlSource(MyCrudProvider.getListSql(myEntityDto)), SqlCommandType.SELECT, Object.class, modelClass, null, null, null);
    }


    private <T> void injectInsertSql(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        MyKeyGeneratorParameter myKeyGenerator = new MyKeyGeneratorParameter(myEntityDto);
        String sql = MyCrudProvider.insertSql(myEntityDto);

        this.addInsertMappedStatement(mapperClass,modelClass,"insert",
                this.createSqlSource(sql),myKeyGenerator.getKeyGenerator(),myKeyGenerator.getKeyProperty(),myKeyGenerator.getKeyColumn());
    }
    private <T> void injectDeleteSql(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        //没有用到动态sql，所以可以用rawsqlsource
        SqlSource sqlSource = new RawSqlSource(configuration,"delete from "+ myEntityDto.getTableName()
                +" where " + myEntityDto.getMyPrimaryKeyDto().getCloumnName() + "= #{" +
        myEntityDto.getMyPrimaryKeyDto().getPropertyName()+ "}",Object.class);
        //少封装一层方法也好，插入多了一层方法，看着也难受
        this.addMappedStatement(mapperClass,"deleteById",sqlSource,SqlCommandType.DELETE,
                Long.class,null,new NoKeyGenerator(),null,null);
    }
    private <T> void injectUpdateByIdSql(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        String sql = MyCrudProvider.updateSql(myEntityDto,false);
        this.addMappedStatement(mapperClass,"updateByEntity",this.createSqlSource(sql),SqlCommandType.UPDATE,
                modelClass,null,new NoKeyGenerator(),null,null);
    }
    private <T> void injectUpdateByWholeEntitySql(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        String sql = MyCrudProvider.updateSql(myEntityDto,true);
        this.addMappedStatement(mapperClass,"updateByWholeEntity",this.createSqlSource(sql),SqlCommandType.UPDATE,
                modelClass,null,new NoKeyGenerator(),null,null);
    }
    private <T> void injectGetByIdSql(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        //项目组还用了createsource方法，感觉可以和删除一样用new raw的，试试
        String sql = MyCrudProvider.getByIdSql(myEntityDto);
        SqlSource sqlSource = new RawSqlSource(configuration,sql,Long.class);
        //这边应该是参数的类型，试试看，结果null，object，long都行，应该是long，保险写object
        this.addMappedStatement(mapperClass,"getEntityById",sqlSource,SqlCommandType.SELECT,
                Object.class,modelClass,null,null,null);
    }

    private <T> void injectGetEntityCount(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        String sql = "select count(0) from " + myEntityDto.getTableName();
        SqlSource sqlSource = new RawSqlSource(configuration,sql,null);
        //这个写null，三个都试试,null会在里面变成object
        this.addMappedStatement(mapperClass,"getEntityCount",sqlSource,SqlCommandType.SELECT,
                Object.class,Object.class,null,null,null);
    }

    private <T> void injectGetValueById(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        //!!!# $ 一般 =#{}因为#会将 值 用 "" 括起来，$只是复制，所以必须用$
        String sql = "select ${fieldName} from " + myEntityDto.getTableName()+ " where "+
                myEntityDto.getMyPrimaryKeyDto().getCloumnName() + " = #{"
                + myEntityDto.getMyPrimaryKeyDto().getPropertyName() + "}";
        //SqlSource sqlSource = new RawSqlSource(configuration,sql,Object.class);
        // 这个RawSqlSource也可以，注释是因为当时stringmessageconverter问题,并且这个问题还没解决
        SqlSource sqlSource = this.createSqlSource(sql);
        this.addMappedStatement(mapperClass,"getValueById",sqlSource,SqlCommandType.SELECT,
                Object.class,Object.class,null,null,null);
        //输入参数和输出参数都用OBJECT对象包装，因为不确定性
    }
    private <T> void injectinsertBatch(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        String sql = MyCrudProvider.insertBatchSql(myEntityDto);
        SqlSource sqlSource = this.createSqlSource(sql);
        MyKeyGeneratorParameter myKeyGenerator = new MyKeyGeneratorParameter(myEntityDto);
        this.addInsertMappedStatement(mapperClass,modelClass,"insertBatch",sqlSource,myKeyGenerator.getKeyGenerator(),
                myKeyGenerator.getKeyProperty(),myKeyGenerator.getKeyColumn());
    }
    //这个方法还是自己扩展的喔哈
    private <T> void injectDeleteBatch(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        String sql = MyCrudProvider.deleteBatchSql(myEntityDto);
        SqlSource sqlSource = this.createSqlSource(sql);
        this.addMappedStatement(mapperClass,"deleteBatch",sqlSource,SqlCommandType.DELETE,
                Long.class,null,new NoKeyGenerator(),//必须是NoKeyGenerator，因为对数据库进行操作了
                null,null);
    }
    //这边主要是add一个mappedstatement，主要还是拦截器完成分页功能
    private <T> void injectgetEntityListForPage(Class<T> mapperClass, Class modelClass, MyEntityDto myEntityDto) {
        String sql = MyCrudProvider.getListSql(myEntityDto);
        SqlSource sqlSource = this.createSqlSource(sql);
        this.addMappedStatement(mapperClass,"getEntityListForPage",sqlSource,
                SqlCommandType.SELECT,Object.class,modelClass,null,null,null);
    }


    private void addInsertMappedStatement(Class<?> mapperClass, Class<?> modelClass, String id, SqlSource sqlSource, KeyGenerator keyGenerator, String keyProperty, String keyColumn) {
        this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.INSERT, modelClass, null, keyGenerator, keyProperty, keyColumn);
    }

    private void addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class<?> modelClass, Class resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) {
        String mappedStateId = mapperClass.getCanonicalName() + id;
        if (this.configuration.hasStatement(mappedStateId))
        {
            System.out.println("已经有xml或者其他provider加载过了");
        }
        else {
            //参数好多啊，受不了了
            this.assistant.addMappedStatement(id,sqlSource, StatementType.PREPARED,sqlCommandType,null,
                    null,null,modelClass,null,resultType,null,
                    false,true,false,keyGenerator,keyProperty,keyColumn,
                    this.configuration.getDatabaseId(),driver);//试试driver能不能复用
        }
    }

    private SqlSource createSqlSource(String sql) {
        //本来是xml的，如果用@select里面写sql包含动态sql的话，就需要<scrrpt>标签包围，
        // 不然可能没用，可以试试,看了下，网上都需要加，活到老，学到老,第三个参数传null不知道为何，应该是modelcalss啊
        return driver.createSqlSource(configuration,"<script> \n\t" + sql + "</script>",null);
    }

}
